#include "Replication.h"

Replication::Replication(short port) : sendBuffer(new char[WBuffLen]()),
									   recvBuffer(new char[RBuffLen]()),
									   mt(rd()),
									   dis(0x01, 0xffff),
									   worldID(0),
									   fromAddrLength(sizeof(sockaddr)),
									   syncTimer(Clock() + FPS)
{
	sockaddr_in hostAddr;
	hostAddr.sin_family = AF_INET;
	hostAddr.sin_port = htons(port);
	hostAddr.sin_addr.s_addr = INADDR_ANY;
#ifdef _WIN32
	WSAData wsadata;
	if (WSAStartup(MAKEWORD(2, 2), &wsadata) == -1)
		perror("ERROE ON WSAStartup!\n");
#endif
	socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
	if (socket_fd == -1)
		perror("ERROE ON CREATEING SOCKET!\n");
	int err = bind(socket_fd, (sockaddr *)&hostAddr, sizeof(hostAddr));
	if (err == -1)
		perror("ERROE ON BINDING SOCKET!\n");
#ifndef _WIN32
	fcntl(socket_fd, F_SETFL, (fcntl(socket_fd, F_GETFL) | O_NONBLOCK));
#else
	u_long io = 1;
	ioctlsocket(socket_fd, FIONBIO, &io);
#endif
}

Replication::~Replication()
{
#ifndef _WIN32
	close(socket_fd);
#else
	closesocket(socket_fd);
#endif
	for (auto iter = EntryObject.begin(); iter != EntryObject.end(); iter++)
		delete iter->second;

	for (auto iter = WorldObject.begin(); iter != WorldObject.end(); iter++)
	{
		for (auto iter1 = iter->second->playerInfo.begin(); iter1 != iter->second->playerInfo.end(); iter1++)
		{
			delete ((RUDP *)iter1->second.RUDPSes);
			delete ((sockaddr *)iter1->second.Addr);
		}
		delete iter->second;
	}

	while (!RudpRecvQueue.empty())
	{
		RUDP::releaseFragment((RUDP::Fragment *)RudpRecvQueue.front());
		RudpRecvQueue.pop();
	}

	delete[] recvBuffer;
	delete[] sendBuffer;
}

void Replication::Run()
{
	int receivedSize = recvfrom(socket_fd, recvBuffer, RBuffLen, 0, &from, &fromAddrLength);
	if (receivedSize > 0)
	{
		uint8_t MSGType;
		char *ptr = recvBuffer;
		std::map<uint32_t, RUDPINFO>::iterator rudp_iterator;

		ptr = RepProtocol::DeserializeMSGType(ptr, MSGType);

		switch (MSGType)
		{
		case Entry_MSG:
			OnEntry(ptr, &from);
			break;
		default:
			rudp_iterator = RudpSession.find(HashCode(&from));
			if (rudp_iterator != RudpSession.end())
				rudp_iterator->second.RUDPSession->Input(recvBuffer, receivedSize, inet_ntoa(((sockaddr_in *)&from)->sin_addr));
			break;
		}
	}
	else
		std::this_thread::sleep_for(std::chrono::milliseconds(1));

	MatchUpdate();
	RudpUpdate();
	Synchronization();
}

void Replication::OnEntry(char *ptr, sockaddr *fromAddr)
{
	// create players and initialize player information
	auto hash = HashCode(fromAddr);
	PlayerEntry PlayerInfo;
	RepProtocol::DeserializeEntryMSG(ptr, PlayerInfo);

	if (RudpSession.find(hash) != RudpSession.end())
		return;

	auto Group = EntryObject.find(PlayerInfo.TeamType);
	if (Group != EntryObject.end())
	{
		auto player = Group->second->find(hash);
		if (player != Group->second->end())
		{
			player->second.ts = this->Clock() + MaxMatchTime;
			return;
		}

		InitializePlayerInfo(PlayerInfo, fromAddr);
		((*Group->second)[hash]) = PlayerInfo;
	}
	else
	{
		InitializePlayerInfo(PlayerInfo, fromAddr);
		EntryObject[PlayerInfo.TeamType] = [&] {
			auto EntryPlayer = new std::map<uint32_t, PlayerEntry>();
			((*EntryPlayer)[hash]) = PlayerInfo;
			return EntryPlayer;
		}();
		Group = EntryObject.begin();
	}

	switch (PlayerInfo.TeamType)
	{
	case 0:
		if (TeamMode::Solo == Group->second->size())
			EnterWorld(Group);
		break;
	case 1:
		if (TeamMode::Dou == Group->second->size())
			EnterWorld(Group);
		break;
	case 2:
		if (TeamMode::Squad == Group->second->size())
			EnterWorld(Group);
		break;
	default:
		break;
	}
}

void Replication::OnReady(char *ptr, sockaddr *fromAddr)
{
	PlayerReady ready;
	auto hash = HashCode(fromAddr);
	RepProtocol::DeserializeReadyMSG(ptr, ready);

	auto team_ = WorldObject.find(ready.WorldID);
	if (team_ != WorldObject.end())
	{
		auto playerinfo = team_->second->playerInfo.find(hash);
		if (playerinfo != team_->second->playerInfo.end())
		{
			if (playerinfo->second.playerId == ready.PlayerID)
				playerinfo->second.ready = true;
		}
	}
}

void Replication::OnControll(char *ptr, sockaddr *fromAddr)
{
	PlayerInput playerInput;
	auto hash = HashCode(fromAddr);
	RepProtocol::DeserializeControllsMSG(ptr, playerInput);
	auto team_ = WorldObject.find(playerInput.WorldID);
	if (team_ != WorldObject.end())
	{
		auto playerControll = team_->second->playerControll.find(hash);
		if (playerControll != team_->second->playerControll.end())
		{
			playerControll->second.push_back(playerInput.Controll);
			team_->second->ts = this->Clock() + MaxWaitPResponsTime;
		}
	}
}

void Replication::RudpUpdate()
{
	for (auto rudpIterator = RudpSession.begin(); rudpIterator != RudpSession.end(); rudpIterator++)
	{
		uint8_t MSGType;
		if (rudpIterator->second.RUDPSession->Recv(RudpRecvQueue))
		{
			while (!RudpRecvQueue.empty())
			{
				auto Data = RudpRecvQueue.front();
				char *ptr = Data->buf;

				ptr = RepProtocol::DeserializeMSGType(ptr, MSGType);

				switch (MSGType)
				{
				case Ready2Play_MSG:
					OnReady(ptr, rudpIterator->second.endpoint);
					break;
				case Controll_MSG:
					OnControll(ptr, rudpIterator->second.endpoint);
					break;
				default:
					break;
				}

				RudpRecvQueue.pop();
				RUDP::releaseFragment((RUDP::Fragment *)Data);
			}
		}
		rudpIterator->second.RUDPSession->Update();
	}
}

void Replication::MatchUpdate()
{
	for (auto iter = EntryObject.begin(); iter != EntryObject.end(); iter++)
	{
		for (auto iter1 = iter->second->begin(); iter1 != iter->second->end();)
		{
			int ts = this->Clock() - iter1->second.ts;
			if (ts >= 0)
			{
				iter1 = iter->second->erase(iter1);
			}
			else
				iter1++;
		}
	}
}

void Replication::Synchronization()
{
	int ts = this->Clock() - syncTimer;
	if (ts >= 0)
	{
		for (auto iter = WorldObject.begin(); iter != WorldObject.end();)
		{
			bool isReady = true;
			PlayerInput p;
			char *ptr = sendBuffer;
			ptr = RepProtocol::SerializeMSGType(ptr, Controll_MSG);
			std::map<uint32_t, std::list<unsigned short>>::iterator iter1;
			std::map<uint32_t, SPlayerInfo>::iterator iter2;
			for (iter1 = iter->second->playerControll.begin(), iter2 = iter->second->playerInfo.begin(); iter1 != iter->second->playerControll.end(); iter1++, iter2++)
			{
				if (!iter2->second.ready)
				{
					isReady = false;
					break;
				}

				p.PlayerID = iter2->second.playerId;
				p.Controll = iter1->second.front();
				ptr = RepProtocol::SerializeControllsMSG(p, ptr);
			}
			if (isReady)
			{
				for (iter2 = iter->second->playerInfo.begin(), iter1 = iter->second->playerControll.begin(); iter2 != iter->second->playerInfo.end(); iter2++, iter1++)
				{
					((RUDP *)iter2->second.RUDPSes)->Send(sendBuffer, ptr - sendBuffer);
					if (iter1->second.size() > 1)
						iter1->second.pop_front();
				}
			}
			ts = this->Clock() - iter->second->ts;
			if (ts >= 0)
			{
				RemoveTeam(iter);
				iter = WorldObject.erase(iter);
			}
			else
				iter++;
		}
		syncTimer = this->Clock() + FPS;
	}
}

void Replication::RemoveTeam(std::map<uint16_t, Team *>::iterator &iter)
{
	for (auto iter1 = iter->second->playerInfo.begin(); iter1 != iter->second->playerInfo.end(); iter1++)
	{
		delete ((RUDP *)iter1->second.RUDPSes);
		delete ((sockaddr *)iter1->second.Addr);
		RudpSession.erase(iter1->first);
	}
	delete iter->second;
}

void Replication::InitializePlayerInfo(PlayerEntry &player, sockaddr *from_addr)
{
	sockaddr *endpoint = new sockaddr;
	*endpoint = *from_addr;

	RUDP *rudpSession = new RUDP(100, 2, 512, RUDP_MSG);
	rudpSession->SetOutput(std::bind(sendto, socket_fd,
									 std::placeholders::_1, std::placeholders::_2,
									 0, (sockaddr *)endpoint, sizeof(sockaddr)));

	player.Addr = endpoint;
	player.RUDPSes = rudpSession;
	player.ts = this->Clock() + MaxMatchTime;
}

void Replication::EnterWorld(std::map<uint8_t, std::map<uint32_t, PlayerEntry> *>::iterator &matched)
{
	while (true)
	{
		worldID = RandomID();
		auto exist = WorldObject.find(worldID);
		if (exist == WorldObject.end())
			break;
	}

	char *ptr = sendBuffer;
	char *ptr1 = recvBuffer;
	uint8_t playerID = 0;
	Team *team = new Team;
	RUDP *rudpSession;
	ptr = RepProtocol::SerializeMSGType(ptr, Entry_MSG);

	for (auto iter = matched->second->begin(); iter != matched->second->end(); iter++)
	{
		iter->second.WorldID = worldID;
		iter->second.PlayerID = ++playerID;
		ptr = RepProtocol::SerializeEntryMSG(iter->second, ptr);

		rudpSession = (RUDP *)iter->second.RUDPSes;
		SPlayerInfo spi{iter->second.PlayerID, false, iter->second.RUDPSes, iter->second.Addr};
		team->playerControll[iter->first].push_back(0x0);
		team->playerInfo[iter->first] = spi;
		team->ts = this->Clock() + MaxWaitPlayerEntryTime;
		RudpSession[iter->first] = RUDPINFO{rudpSession, (sockaddr *)iter->second.Addr};

		ptr1 = RepProtocol::SerializeMSGType(ptr1, GainControlID_MSG);
		ptr1 = RepProtocol::SerializeGainControlIDMSG(iter->second.PlayerID, worldID, ptr1);
		rudpSession->Send(recvBuffer, ptr1 - recvBuffer);
		ptr1 = recvBuffer;
	}
	for (auto iter = matched->second->begin(); iter != matched->second->end(); iter++)
		((RUDP *)iter->second.RUDPSes)->Send(sendBuffer, ptr - sendBuffer);

	WorldObject[worldID] = team;
	delete matched->second;
	EntryObject.erase(matched);
}

uint16_t Replication::RandomID()
{
	return dis(mt);
}

uint32_t Replication::HashCode(sockaddr *addr)
{
	return ((sockaddr_in *)addr)->sin_addr.s_addr / ((sockaddr_in *)addr)->sin_port;
}

uint32_t Replication::Clock()
{
	return std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now()).time_since_epoch().count() & 0xffffffff;
}